<?php

namespace nimo\domains\acc\batch\csv;

use DateTime;
use DB;
use Validator;
use nimo\common\csv\CsvConverter;
use nimo\common\csv\SimpleConverter;
use nimo\common\csv\SimpleInterceptor;
use nimo\common\csv\Importer;
use nimo\common\design\IVisitor;
use nimo\common\design\PComposite;
use nimo\common\validator\BatchValidator;

class WakuMasterCodeCsvImportConverter implements IVisitor {
    protected $depth = 0;
    public function __construct($configs = array()) {
        $this->configs = $configs;
        if (! isset($configs['domain'])) {
            throw new \Exception('make sure your domain');
        }
    }
    public static function import($file, $params = array()) {
        $params['domain'] = 0;
        $validators = array(
            '状態'         => 'integer',
            '現在コード'   => 'digits:4',
            '変更コード'   => '',
            '名称'         => 'max:40',
            '読み'         => 'max:40',
            '略称'         => 'max:20',
            '有効期限'     => '',
            '貸借区分'     => 'in:借方,貸方,',
            '帳簿使用区分' => '',
            'CVP集計区分'  => 'in:売上,固定,変動',
            '科目説明'     => 'max:120',
            '借方税区分'   => 'in:税計算対象外,有価証券の譲渡,課税仕入(課売対応),課税売上,非課税売上,課税売上返還,課税仕入返還(課売対応),課税貸倒れ',
            '貸方税区分'   => 'in:税計算対象外,有価証券の譲渡,課税仕入(課売対応),課税売上,非課税売上,課税売上返還,課税仕入返還(課売対応),課税貸倒れ',
            '自動計算区分' => 'in:自動計算しない,経理規定に準ずる',
        );
        $m_balance = array('借方' => 1,'貸方' => 2);
        $m_cvp_type = array('売上' => 1,'固定' => 2,'固定' => 3);
        $m_tax = array('税計算対象外' => '1','有価証券の譲渡' => '2','課税仕入(課売対応)' => '3','課税売上' => '4','非課税売上' => '5','課税売上返還' => '6','課税仕入返還(課売対応)' => '7','課税貸倒れ' => '8');
        $m_calctype = array('自動計算しない' => '1','経理規定に準ずる' => '2');

        $prev_depth = 0;

        $converters = array(
            'domain'        =>  null,                                          // ドメイン
            'id'            =>  null,                                          // ID
            'code'          =>  1,                                             // 勘定コード 現在コード
            'balance'       =>  function($fields) use($m_balance) {            // balance(debt, 2:credit) 貸借区分
                return array_key_exists($fields[7], $m_balance) ? $m_balance[$fields[7]] :0;
            },
            'element'       =>  null,                                          // 要素名
            'name'          =>  3,                                             // ラベル,名称
            'sname'         =>  5,                                             // 略称ラベル
            'lname'         =>  null,                                          // 冗長ラベル
            'uname'         =>  null,                                          // 用途別ラベル
            'spell'         =>  4,                                             // 読み
            'type'          =>  null,                                          // type (0:string, 1:money)
            'abstract'      =>  null,                                          // abstract (0:false, 1:true)
            'cvp_type'      =>  function($fields) use($m_cvp_type) {           // CVP集計区分 (1:売上,2:固定,3:変動)
                return array_key_exists($fields[9], $m_cvp_type) ? $m_cvp_type[$fields[9]] :null;
            },
            'calclute_type' =>  function($fields) use($m_calctype) {           // 自動計算区分 (1:自動計算対象外, 2:経理規定対象)
                return array_key_exists($fields[13], $m_calctype) ? $m_calctype[$fields[13]] :null;
            },
            'tax_debt'      =>  function($fields) use($m_tax) {                // 借方税区分
                return array_key_exists($fields[11], $m_tax) ? $m_tax[$fields[11]] :null;
            },
            'tax_credit'    =>  function($fields) use($m_tax) {                // 貸方税区分
                return array_key_exists($fields[12], $m_tax) ? $m_tax[$fields[12]] :null;
            },
            'memo'          =>  10,                                            // 科目説明
            'node_depth'    =>  function($fields) use(&$prev_depth) {          // 状態 -> depth
                if (is_numeric($depth = $fields[0])) {
                    $prev_depth = $depth;
                } else {
                    $depth = $prev_depth + 1;
                }
                return $depth;
            }
        );

        // read file & keep data in si
        $si = new SimpleInterceptor(new BatchValidator(array_values($validators)), new SimpleConverter($converters), array('skipFirst' => true));
        $configs = array('fromCharset' => 'Shift-JIS');
        $ci = new Importer($file, $configs, $si);
        $ci->execute();
        if (count($messages = $si->messages())) {
            return $messages;
        }

        // get readed data
        $lines = $si->getDatas();

        $converter = new WakuMasterCodeCsvImportConverter($params);
        // create composite model
        $roots = PComposite::makeTreeByDepth($lines, $converter);

        // calculate tree model
        $converter->step = 1;
        $converter->nestid = 0;
        $converter->nestbound = 0;
        $converter->indent = - 1;
        foreach ( $roots as $root ) {
            $root->visit($converter);
        }

        // create record & write to database
        $converter->step = 2;
        $converter->indent = - 1;
        DB::transaction(function () use($converter, $roots, $params) {
            $converter->batchtime = new DateTime();
            DB::table('acc_mastercode')->where('domain', '=', $params['domain'])->delete();
            foreach ( $roots as $root ) {
                $root->visit($converter);
            }
            if (count($converter->datas)) {
                DB::table('acc_mastercode')->insert($converter->datas);
            }
            $count = DB::table('acc_mastercode')->where('domain', '=', $params['domain'])->count();
            echo "inserted $count\n";
        });
    }

    // PComposite::depth
    public function depth(&$csv) {
        if (! is_numeric($depth = $csv['node_depth'])) {
            throw new \Exception('make sure your node depth');
        }
        return $depth;
    }
    // PComposite::validate
    public function validate($csv) {
        // mapped data
        if (! is_array($csv)) {
            return false;
        }
        // parent or child node ?
        return (is_numeric($csv['node_depth']) || is_numeric($csv['code']));
    }
    private $step = 0;
    private $nestid = 0;
    private $nestbound = 0;
    private $indent = 0;
    private $datas = array();
    private $batchtime = null;
    private $batch_size = null;

    // IVisitor::entering
    public function entering($visitor) {
        $this->indent ++;
        if ($this->step == 1) {
            // STEP1
            // PComposite
            $this->nestid ++;
            $this->nestbound ++;
            // tree
            $visitor->set_field('id', $this->nestid);
            if ($visitor->parent()) {
                $fields = $visitor->parent()->fields();
                $parent = $fields['id'];
            } else {
                $parent = 0;
            }
            $visitor->set_field('node_parent', $parent);
            $visitor->set_field('node_left', $this->nestbound);
            $visitor->set_field('node_depth', $this->indent);
        }
    }
    // IVisitor::visit
    public function visit($visitor) {
        if ($this->step == 2) {
            // STEP2
            // PComposite
            $data = $visitor->fields();
            if (empty($data['code'])) {
                throw new \Exception('make sure code:' . json_encode($data));
            }
            // etc
            $data['domain'] = $this->configs['domain'];
            $data['created_at'] = $this->batchtime;
            $data['updated_at'] = $this->batchtime;
            $pdata = $visitor->parent() && $visitor->parent()->fields();
            if ($this->batch_size === null) {
                $configs = app('config');
                $max_bind_number = (isset($configs['database.max_bind_number'])) ? $configs['database.max_bind_number'] : 999;
                $this->batch_size = intval($max_bind_number / count($data)) - 1;
            }
            $this->datas[] = $data;
            if (count($this->datas) >= $this->batch_size) {
                DB::table('acc_mastercode')->insert($this->datas);
                $this->datas = array();
            }
        }
    }
    // IVisitor::leaving
    protected $parent_code = 0;
    public function leaving($visitor) {
        $this->indent --;
        if ($this->step == 1) {
            // STEP1
            $this->nestbound ++;
            // PComposite
            $visitor->set_field('node_right', $this->nestbound);
            // code calculate
            $parents = array();
            if (is_numeric($code = $visitor->fields()['code']) || ! $visitor->has_child()) {
                $p = $visitor;
                // check parents
                while ( $p != null ) {
                    if (empty($p->fields()['code'])) {
                        $parents[] = $p;
                    }
                    $p = $p->parent();
                }
            }

            for($i = count($parents) - 1; $i >= 0; $i --) {
                $fields = $parents[$i]->fields();
                if ($fields['node_depth'] == 0) {
                    $this->parent_code = intval(($code / 1000) * 100);
                } else if ($fields['node_depth'] == 1) {
                    $c1 = intval(($code / 100) * 10);
                    if ($c1 > $this->parent_code) {
                        $this->parent_code = $c1;
                    } else {
                        $this->parent_code ++;
                    }
                } else {
                    $this->parent_code ++;
                }
                //echo ("$this->parent_code make sure code:" . json_encode($fields, JSON_UNESCAPED_UNICODE) . "\n");
                $parents[$i]->set_field('code', $this->parent_code);
            }
        }
    }
}
